package atom.core0.socket;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;

import org.apache.log4j.Logger;

import atom.core0.bean.general.MyProtocalPack;
import atom.core0.exception.BizConnectException;
import atom.core0.exception.BizException;
import atom.core0.exception.UnknowStateException;
import atom.core0.object.ObjectUtil;

//独立出客户端socket类，目的是为了防止忘记设置超时
//以及收发
//同一台机器 只发100M，不收，等接收时，会有问题。消息都没了。
//BufferedOutputStream的数据成员buf是一个位数组，默认为512字节。当使用write()方法写入数据时，实际上会先将数据写至buf中，当buf已满时才会实现给定的OutputStream对象的write()方法

//BufferedInputStream的数据成员buf是一个位数组，默认为2048字节。当读取数据来源时，例如文件，BufferedInputStream会尽量将buf填满。
//当使用read()方法时，实际上是先读取buf中的数据，而不是直接对数据来源作读取。当buf中的数据不足时，BufferedInputStream才会再实现给定的InputStream对象的read()方法，从指定的装置中提取数据。

public class ClientSocket
{
	protected static Logger logger = Logger.getLogger(ClientSocket.class);
	private Socket	     socket;
	// BufferedOutputStream BufferedInputStream
	private OutputStream	os;
	private InputStream	 is;
	private int	         socketFlag	= 0; // -1连接断开 1正常 0超时
	//private int	         tryTimes	= 5;
	private int innerTimeout = 100;//一个包多次读时，内部设定的超时时间ms

	public int getInnerTimeout()
    {
    	return innerTimeout;
    }

	public void setInnerTimeout(int innerTimeout)
    {
    	this.innerTimeout = innerTimeout;
    }

	public int getSocketFlag()
	{
		return socketFlag;
	}

	public void setSocketFlag(int socketFlag)
	{
		this.socketFlag = socketFlag;
	}

	public Socket getSocket()
	{
		return socket;
	}

	public void setSocket(Socket socket)
	{
		this.socket = socket;
	}

	public ClientSocket() {
	}
	public ClientSocket(Socket s) {
		socket = s;
	}
	public void attach(Socket s, int readTimeout)
	{
		try
		{
			s.setSoTimeout(readTimeout * 1000);
			//s.setSoLinger(on, linger);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		socket = s;
	}

	private Socket connect(String hostname, int port, int connectTimeout) throws IOException
	{
		try
		{
			Socket socket = new Socket();
			SocketAddress socketAddress = new InetSocketAddress(hostname, port);
			socket.connect(socketAddress, connectTimeout * 1000);
			return socket;
		}
		catch (BindException e)
		{
			e.printStackTrace();
		}
		return null;

	}

	// readTimeout 秒
	public Socket connect(String hostname, int port, int connectTimeout, int readTimeout) throws Exception
	{
		Socket s = null;
		int	         tryTimes	= 5;
		while (tryTimes > 0)
		{
			tryTimes--;
			s = connect(hostname, port, connectTimeout);
			if (s != null)
				break;
		}
		if (s == null)
			throw new BizConnectException("连续[" + tryTimes + "]次创建socket失败[" + hostname + ":" + port + "]");

		s.setSoTimeout(readTimeout * 1000);
		socket = s;
		return s;
	}

	public void writen(byte[] buffer) throws IOException
	{
		writen(buffer, 0, buffer.length);
	}

	synchronized public void writen(String buffer) throws IOException
	{
		byte[] bytes = buffer.getBytes("GBK");
		writen(bytes, 0, bytes.length);
	}

	// 如果只写不读，大概130M左右，就无法再写了。
	public void writen(byte[] buffer, int offset, int len) throws IOException
	{
		//if (os == null)
			os = socket.getOutputStream();

		os.write(buffer, offset, len);
		os.flush();

	}

	public void write(File msgFile) throws Exception
	{
		os = socket.getOutputStream();

		FileInputStream fin = new FileInputStream(msgFile);
		byte[] buf = new byte[1024];
		int num;
		while ((num = fin.read(buf, 0, buf.length)) != -1)
		{
			os.write(buf, 0, num);
		}
		os.flush();

	}

	public String readString() throws Exception
	{
		BufferedWriter out = null;
		BufferedReader in = null;
		StringBuffer readBuf = new StringBuffer();
		try
		{
			if (in == null)
				in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
			String line = null;
			int indexId = 0;
			while ((line = in.readLine()) != null)
			{
				if (indexId > 0)
					readBuf.append(System.getProperty("line.separator"));
				readBuf.append(line);
				indexId += 1;
			}
		}
		catch (InterruptedIOException ex)
		{
			throw new UnknowStateException(ex);
		}
		finally
		{
			if (in != null)
				in.close();
			if (out != null)
				out.close();
		}
		return readBuf.toString();
	}

	//timeout 秒
	public int readn(byte[] buffer,int timeout) throws IOException
	{
		socket.setSoTimeout(1000*timeout);
		return readn(buffer, 0, buffer.length);
	}
	public int readn(byte[] buffer) throws IOException
	{
		return readn(buffer, 0, buffer.length);
	}
	//timeout 秒
	public int readn(byte[] buffer, int offset, int len,int timeout) throws IOException
	{
		socket.setSoTimeout(1000*timeout);
		return readn(buffer,offset,len);
	}
	
	synchronized public Serializable readObject(int len) throws Exception
	{
		byte[] buffer = new byte[len];
		int nRead = readn(buffer, 0, len);
		if (nRead==0)
			return null;
		int objlen = Integer.valueOf(new String(buffer));

		byte[] bufferObj = new byte[objlen];
		readn(bufferObj, 0, objlen);
		Serializable obj = (Serializable) ObjectUtil.bytes2Object(bufferObj);
		return obj;

	}

	synchronized public void writeObject(Serializable obj, int len) throws Exception
	{
		byte[] bb = ObjectUtil.object2Bytes(obj);
		String format = "%0" + len + "d";
		String slen = String.format(format, bb.length);
		writen(slen);
		writen(bb);

	}	
	//基本的读函数
	synchronized public int readn(byte[] buffer, int offset, int len) throws IOException
	{
		// int nbytes = buffer.length;
		int nCount=0;
		int nleft;
		int nread=0;
		int ptr = 0;
		nleft = len;
		//if (is == null)
			is = socket.getInputStream();
		long nStartTime = System.currentTimeMillis();
		int orgTimeout = socket.getSoTimeout();
		while (nleft > 0)
		{
			nCount++;
			try
			{
				//优化：一个数据报，分多次来的时候，第一次按正常的读超时时间。第二次以后，设定500毫秒
				//如果500毫秒超时了，说明网络不好，或者可能被恶意攻击
				//如果不这么做，客户端设定了10秒，实际上可能20秒才返回，因为第二次等待会继续等待10秒
				if(nCount>1)
				{
					long nEndTime = System.currentTimeMillis();
					int d  = orgTimeout - (int)(nEndTime - nStartTime);
					if (d<=0)
						d = innerTimeout;
					logger.warn("网络不好,一个包第"+nCount+"次到达,剩余字节["+nleft+"/"+len+"].超时时间设置为"+d+"毫秒");
					socket.setSoTimeout(d);//innerTimeout
				}
				nread = is.read(buffer, offset + ptr, len - ptr);
			}
			catch (SocketTimeoutException e)
			{
				// 超时
				socketFlag = 0;
				nread = 0;
			}
			if(nCount>1)
			{
				socket.setSoTimeout(orgTimeout);
			}
			
			// 断连接
			if (nread < 0)
			{
				socketFlag = -1;
				break;
			}
			// 超时后转换为读到0字节
			else if (nread == 0)
			{
				socketFlag = 0;
				break;
			}
			else
			// 至少读1个字节
			{
				socketFlag = 1;
				nleft -= nread;
				ptr += nread;
			}

		}
		//当断开连接时，第一次返回读到的字节数。再次读的时候返回-1
		int nReturn = len - nleft;
		if (nread < 0 && nReturn==0)
			return -1;
		else
			return nReturn;
	}
	synchronized public void writePack(MyProtocalPack pack) throws Exception
	{
		writen(pack.getMsgHead());
		writen(pack.getMsgBody());
	}
	synchronized public MyProtocalPack readPack() throws Exception
	{
		byte[] head= new byte[38];
		int ret = readn(head);
		if (ret!=head.length)
			throw new BizException("超时,读到的head信息不完整["+ret+"/38]");
		byte[] flag = new byte[2];
		byte[] id = new byte[28];
		byte[] blen = new byte[8];
		System.arraycopy(head, 0, flag, 0, 2);
		System.arraycopy(head, 2, id, 0, 28);
		System.arraycopy(head, 30, blen, 0, 8);
		
		int size = Integer.valueOf(new String(blen));
		
		byte[] body = new byte[size];
		ret = readn(body);
		if (ret!=body.length)
			throw new BizException("超时,读到的body信息不完整["+ret+"/"+size+"]");
		
		return new MyProtocalPack(new String(flag),new String(id),body);
		
		
		
	}

	public void close() throws IOException
	{
		if (is != null)
		{
			is.close();
			is=null;
		}
		if (os != null)
		{
			os.close();
			os=null;
		}
		if (socket != null)
		{
			socket.close();
			socket=null;
		}
	}

}
